package com.atguigu.gmall.cart.service.impl;

import com.atguigu.gmall.cart.mapper.CartInfoMapper;
import com.atguigu.gmall.cart.service.CartAsyncService;
import com.atguigu.gmall.cart.service.CartService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author mqx
 * @date 2020-10-19 15:28:38
 */
@Service
public class CartServiceImpl implements CartService {

    @Autowired
    private CartInfoMapper cartInfoMapper;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CartAsyncService cartAsyncService;

    @Override
    public void addToCart(Long skuId, String userId, Integer skuNum) {
        /*
            数据是保存到mysql+redis
        1.  先判断购物车中是否有该商品
        2.  true:   有的话，数量相加
            false:  直接添加到购物车

            以上都是操作的数据库
        3.  将数据同步到缓存中！
         */

        //  分析使用那种数据类型 Hash hset(key,field,value)
        //  key=user:userId:cart field=skuId value=cartInfo.toString();
        //  ，第二个使用的key 如何定义?
        String userCartkey = getUserCartkey(userId);
        //  如果当前缓存中没有用户的购物车，
        if (!redisTemplate.hasKey(userCartkey)){
            //  查询数据库并将数据加载到缓存中
            this.loadCartCache(userId);
        }

        //  select * from cart_info where user_id =? and sku_id=?
        QueryWrapper<CartInfo> cartInfoQueryWrapper = new QueryWrapper<>();
        cartInfoQueryWrapper.eq("user_id",userId).eq("sku_id",skuId);
        CartInfo cartInfoExist = cartInfoMapper.selectOne(cartInfoQueryWrapper);

        //  判断：
        if (cartInfoExist!=null){
            //  购物车中有当前商品
            cartInfoExist.setSkuNum(cartInfoExist.getSkuNum()+skuNum);
            //  初始化当前的商品实时价格    skuInfo.price
            cartInfoExist.setSkuPrice(productFeignClient.getSkuPrice(skuId));
            //  更新数据库
            //  cartInfoMapper.updateById(cartInfoExist);
            cartAsyncService.updateCartInfo(cartInfoExist);
            //  操作缓存
            //  redisTemplate.opsForHash().put(userCartkey,skuId,cartInfoExist);
        }else {
            //  购物车中没有当前商品
            CartInfo cartInfo = new CartInfo();
            //  cart_info 表中的数据来源于谁?    service-product;
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);

            cartInfo.setUserId(userId);
            cartInfo.setSkuPrice(skuInfo.getPrice());
            cartInfo.setCartPrice(skuInfo.getPrice());
            cartInfo.setSkuNum(skuNum);
            cartInfo.setSkuId(skuId);
            cartInfo.setSkuName(skuInfo.getSkuName());
            cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());

            //  调用插入方法
            //  cartInfoMapper.insert(cartInfo);
            //  异步操作数据库！ id 声明时候才会获取到自动生成的值？    只有当mapper 执行insert的时候，才会自动生成！
            cartAsyncService.saveCartInfo(cartInfo);
            // 废物利用：
            cartInfoExist = cartInfo;
            //  添加到缓存！
            //  redisTemplate.opsForHash().put(userCartkey,skuId,cartInfo);
        }
        //  操作缓存
        redisTemplate.boundHashOps(userCartkey).put(skuId.toString(),cartInfoExist);
        //  设置缓存的过期时间   setCartKeyExpire
        //  set key,value,px,1000,nx;   expire();
        setCartKeyExpire(userCartkey);

    }

    @Override
    public List<CartInfo> getCartList(String userId, String userTempId) {
        //  声明一个集合
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  未登录：!userId=userTempId
        if (StringUtils.isEmpty(userId)){
            //  根据userTempId 查询数据
            cartInfoList = getCartList(userTempId);
        }
        //  登录：userId
        if (!StringUtils.isEmpty(userId)){
            //  有可能发生合并，userTempId cookie ； 365
            //  获取临时购物车数据
            //  第一种：
//            if(StringUtils.isEmpty(userTempId)){
//                //  不合并，
//                //  根据userId 查询
//                cartInfoList = getCartList(userId);
//            }

            List<CartInfo> cartInfoTmpList = getCartList(userTempId);

            //  第一种：
            //  判断登录了，但是未登录购物车没有数据
//            if (CollectionUtils.isEmpty(cartInfoTmpList)){
//                //  不合并，
//                //  根据userId 查询
//                cartInfoList = getCartList(userId);
//            }
            //  临时购物车有数据
            if (!CollectionUtils.isEmpty(cartInfoTmpList)){
                //  开始合并
                cartInfoList = mergeToCartList(cartInfoTmpList,userId);

                //  合并完成之后，删除未登录购物车数据
                deleteCartList(userTempId);
            }
            //  第二种：
            if (CollectionUtils.isEmpty(cartInfoTmpList)){
                cartInfoList = getCartList(userId);
            }

        }
        //  返回数据
        return cartInfoList;
    }

    @Override
    public void checkCart(String userId, Integer isChecked, Long skuId) {

        //  更新mysql
        cartAsyncService.checkCart(userId,isChecked,skuId);

        //  更新redis
        //  获取key
        String userCartkey = getUserCartkey(userId);

        //  获取redis 中的数据
        BoundHashOperations boundHashOperations = redisTemplate.boundHashOps(userCartkey);
        if (boundHashOperations.hasKey(skuId.toString())){
            //  从缓存中获取到了数据并修改
            CartInfo cartInfo = (CartInfo) boundHashOperations.get(skuId.toString());
            cartInfo.setIsChecked(isChecked);

            //  放入缓存
            boundHashOperations.put(skuId.toString(),cartInfo);
        }
    }

    @Override
    public void deleteCart(Long skuId, String userId) {
        //  删除mysql
        cartAsyncService.deleteCartInfo(userId,skuId);

        //  删除redis
        String userCartkey = getUserCartkey(userId);

        //  获取redis 中的数据
        BoundHashOperations boundHashOperations = redisTemplate.boundHashOps(userCartkey);

        if (boundHashOperations.hasKey(skuId.toString())){
            //  删除单个商品
            //  redisTemplate.delete(userCartkey);  这个相当于删除整个购物车
            boundHashOperations.delete(skuId.toString());
        }
    }

    @Override
    public List<CartInfo> getCartCheckedList(String userId) {
        //  声明对象
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  缓存！user:userId:cart ,  缓存会出现没有数据的情况么? 不会。
        //  获取key
        String userCartkey = getUserCartkey(userId);

        //  List values = redisTemplate.boundHashOps(userCartkey).values();
        List<CartInfo>  cartInfoRdisList = redisTemplate.opsForHash().values(userCartkey);
        //  获取选中状态的商品
        for (CartInfo cartInfo : cartInfoRdisList) {
            if (cartInfo.getIsChecked().intValue()==1){
                cartInfoList.add(cartInfo);
            }
        }
        //  直接返回
        return cartInfoList;
    }

    //  删除
    private void deleteCartList(String userTempId) {
        //  删除未登录购物车数据  redis , mysql
//        QueryWrapper<CartInfo> cartInfoQueryWrapper = new QueryWrapper<>();
//        cartInfoQueryWrapper.eq("user_id",userTempId);
//        cartInfoMapper.delete(cartInfoQueryWrapper);
        cartAsyncService.deleteCartInfo(userTempId);
        //  删除缓存
        String userCartkey = getUserCartkey(userTempId);
        //  判断
        if (redisTemplate.hasKey(userCartkey)){
            redisTemplate.delete(userCartkey);
        }

    }
    //  合并
    private List<CartInfo> mergeToCartList(List<CartInfo> cartInfoTmpList, String userId) {
        /*
            1.  获取登录的购物车数据
            2.  根据条件{skuId}进行合并
         */
        List<CartInfo> cartInfoDBList = getCartList(userId);
        //  第一种：两个集合开始遍历    第二种方案：  将这个集合变成map (key,vlaue)  key = skuId value=cartInfo
        //  集合有一个包含的方法 (skuId);
        //  Function    R apply(T t)
        //  Collectors.toMap();
        Map<Long, CartInfo> cartInfoMapLogin = cartInfoDBList.stream().collect(Collectors.toMap(CartInfo::getSkuId, cartInfo -> cartInfo));
        //  循环遍历
        for (CartInfo cartInfoNoLogin  : cartInfoTmpList) {
            //  获取未登录的skuId
            Long skuId = cartInfoNoLogin .getSkuId();
            //  登录购物车与未登录购物车中有相同的商品
            if (cartInfoMapLogin.containsKey(skuId)){
                //  商品的数量相加，加完之后给数据
                //  获取到当前登录的购物车对象
                CartInfo cartInfoLogin = cartInfoMapLogin.get(skuId);
                cartInfoLogin.setSkuNum(cartInfoLogin.getSkuNum()+cartInfoNoLogin.getSkuNum());
                //  细节处理： 选中状态以未登录为基准
                if (cartInfoNoLogin.getIsChecked().intValue()==1){
                    cartInfoLogin.setIsChecked(1);
                }
                //  37,38
                //  更新数据
                cartAsyncService.updateCartInfo(cartInfoLogin);
            }else {
                //  未登录的商品，在登录的购物车中不存在！
                //  修改用户Id
                //  39
                cartInfoNoLogin.setUserId(userId);
                cartAsyncService.saveCartInfo(cartInfoNoLogin);
            }
        }

        //  获取最终合并的结果！  37,38,39
        List<CartInfo> cartInfoList = loadCartCache(userId);
        return cartInfoList;
    }

    /**
     * 查询购物车数据方法
     * @param userId
     * @return
     */
    private List<CartInfo> getCartList(String userId) {
        //  判断用户Id 为空，则直接返回。
        if (StringUtils.isEmpty(userId)){
            return null;
        }
        //  查看缓存！
        //  定义一个缓存的key
        String userCartkey = getUserCartkey(userId);
        //  使用redis客户端获取数据
        //  hset(key,field,value)   key = userCartkey ,field = skuId ,value=cartInfo.toString();
        //  hash 获取所有数据 hvals(); hget(key,field);
        //  redisTemplate.opsForHash().values(userCartkey);
        List<CartInfo> cartInfoList = redisTemplate.boundHashOps(userCartkey).values();
        if (CollectionUtils.isEmpty(cartInfoList)){
            //  还可以查看数据 selectDB;   放入缓存！
            cartInfoList = loadCartCache(userId);
            return cartInfoList;
        }else {
            //  从缓存中获取到了数据
            //  使用Id 进行排序！
            cartInfoList.sort(new Comparator<CartInfo>() {
                @Override
                public int compare(CartInfo o1, CartInfo o2) {
                    //  String , Long 都可以比较
                    //  str1 = "abc" str2 = "abd"
                    return o1.getId().compareTo(o2.getId());
                }
            });
        }
        return cartInfoList;
    }

    //  根据用户Id 查询数据库并将数据放入缓存
    public List<CartInfo> loadCartCache(String userId) {
        //  如何查询数据
        //  select * from cart_info where user_id = ？
        List<CartInfo> cartInfoList = cartInfoMapper.selectList(new QueryWrapper<CartInfo>().eq("user_id", userId));
        //  数据库中如果为空，则返回！不为空放入缓存！
        if (CollectionUtils.isEmpty(cartInfoList)){
            return cartInfoList;
        }
        //  声明一个map
        HashMap<String, CartInfo> hashMap = new HashMap<>();
        //  将数据放入缓存
        //  获取到缓存的key
        String userCartkey = getUserCartkey(userId);

        //  循环遍历集合将数据放入map
        for (CartInfo cartInfo : cartInfoList) {
            //  细节： 缓存中没有数据，从数据获取到最新数据！ 购物车中的价格，有可能会发生变化
            cartInfo.setSkuPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));
            hashMap.put(cartInfo.getSkuId().toString(),cartInfo);
        }

        //  hset(key,field,value); hmset(key,map);
        //  redisTemplate.boundHashOps().putAll();
        redisTemplate.opsForHash().putAll(userCartkey,hashMap);

        //  设置缓存的过期时间
        setCartKeyExpire(userCartkey);

        return cartInfoList;
    }

    //  设置购物车的过期时间
    private void setCartKeyExpire(String userCartkey) {
        redisTemplate.expire(userCartkey, RedisConst.USER_CART_EXPIRE, TimeUnit.SECONDS);
    }

    //  获取购物车的key
    private String getUserCartkey(String userId) {
        String cartKey = RedisConst.USER_KEY_PREFIX+userId+RedisConst.USER_CART_KEY_SUFFIX;
        return cartKey;
    }
}
